/******************************************************************************
* 	Copyright (c) 2007-2008 All rights reserved
*		Asemantics S.r.l
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer. 
*
* 2. Redistributions in binary form must reproduce the above copyright
*    notice, this list of conditions and the following disclaimer in
*    the documentation and/or other materials provided with the
*    distribution.
*
* 3. The end-user documentation included with the redistribution,
*    if any, must include the following acknowledgment:
*       "This product includes software developed by 
*	 Asemantics S.r.l."
*    Alternately, this acknowledgment may appear in the software itself,
*    if and wherever such third-party acknowledgments normally appear.
*
* 4. All advertising materials mentioning features or use of this software
*    must display the following acknowledgement:
*    This product includes software developed by Asemantics S.r.l.
*    the Semantic Web company, Rome, London, Leiden and its contributors. 
*
* 5. Neither the name of the company nor the names of its contributors
*    may be used to endorse or promote products derived from this software
*    without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Author: Andrea Marchesini - baku@asemantics.com
*
******************************************************************************/

#include <controller.h>
#include <configure.h>
#include <process.h>
#include <istance.h>

static gboolean configure_parser (nxml_t * nxml, TBGlobal * data,
				  GError ** error);
static gboolean configure_parser_size (gchar * string, guint64 * ret_size,
				       GError ** error);
static gboolean configure_log_verbose (gchar * string, LogVerbose * verbose,
				       GError ** error);
static gboolean configure_env_parser (TBGlobal * data, nxml_data_t * cur,
				      GError ** error);
static gboolean configure_network_parser (nxml_t * nxml, TBGlobal * data,
					  GError ** error);

/* BASE CONFIGURE PARSER ***************************************************/

TBGlobal *
configure_init (int argc, char **argv, GError ** error)
{
  TBGlobal *data;
  nxml_t *nxml;
  nxml_data_t *cur;
  gchar **items = NULL;

  gboolean option_list = FALSE;
  gchar *option_process = NULL;

  GOptionContext *context;
  GOptionEntry entries[] = {
    {"list", 'l', 0, G_OPTION_ARG_NONE, &option_list,
     "Show the list of processes", NULL},
    {"process", 'p', 0, G_OPTION_ARG_STRING, &option_process,
     "Exec a single process and quit", "process"},
    {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &items, NULL,
     NULL},
    {NULL}
  };

  /* Menu */
  context = g_option_context_new ("configFile");
  g_option_context_add_main_entries (context, entries, NULL);

  if (g_option_context_parse (context, &argc, &argv, error) == FALSE)
    {
      g_option_context_free (context);
      return NULL;
    }

  g_option_context_free (context);

  /* New data struct: */
  data = g_malloc0 (sizeof (TBGlobal));

  if (!items || !items[0])
    data->configfile = g_strdup (TB_CONTROLLER_CONFIG_FILE);

  else if (!items[1])
    data->configfile = g_strdup (items[0]);

  else
    {
      g_strfreev (items);
      configure_free (data);

      g_set_error (error, controller_error_quark (), 0,
		   "Only one configFile");
      return NULL;
    }

  g_strfreev (items);

  /* Parsing del file */
  if (nxml_new (&nxml) != NXML_OK)
    {
      configure_free (data);

      g_set_error (error, controller_error_quark (), 0,
		   "Error initializing the NXML library");
      return NULL;
    }

  if (nxml_parse_file (nxml, data->configfile) != NXML_OK
      || nxml_root_element (nxml, &cur) != NXML_OK
      || strcmp (cur->value, TB_CONTROLLER_ROOT_TAG))
    {
      g_set_error (error, controller_error_quark (), 0,
		   "Error parsing the XML file: %s", data->configfile);

      configure_free (data);
      return NULL;
    }

  /* Global section: */
  if (configure_parser (nxml, data, error) == FALSE)
    {
      nxml_free (nxml);
      configure_free (data);
      return NULL;
    }

  /* Network section: */
  if (configure_network_parser (nxml, data, error) == FALSE)
    {
      nxml_free (nxml);
      configure_free (data);
      return NULL;
    }

  /* Processes section: */
  if (process_parser (data, nxml, &data->processes, FALSE, error) == FALSE)
    {
      nxml_free (nxml);
      configure_free (data);
      return NULL;
    }

  nxml_free (nxml);

  /* Just a check: */
  if (!data->processes)
    {
      g_set_error (error, controller_error_quark (), 0,
		   "No processes in the XML config file: %s",
		   data->configfile);
      configure_free (data);
      return NULL;
    }

  if (option_list)
    data->operation = TB_OPERATION_LIST;

  if (option_process)
    {
      GList *list;
      gboolean founded = FALSE;

      data->operation = TB_OPERATION_PROCESS;

      for (list = data->processes; list; list = list->next)
	{
	  TBProcess *process = list->data;

	  if (founded == FALSE && process->global == TRUE
	      && !g_utf8_collate (process->id, option_process))
	    {
	      process->is_to_schedule = TRUE;
	      founded = TRUE;
	    }

	  process->scheduled = 0;
	}

      if (founded == FALSE)
	{
	  g_set_error (error, controller_error_quark (), 0,
		       "The process '%s' doesn't exist or it's not global.",
		       option_process);
	  configure_free (data);
	  g_free (option_process);
	  return NULL;
	}

      g_free (option_process);
    }

  data->swap.mutex = g_mutex_new ();
  data->mutex = g_mutex_new ();

  return data;
}

static gboolean
configure_parser (nxml_t * nxml, TBGlobal * data, GError ** error)
{
  nxml_data_t *cur;
  gchar *tmp;

  nxml_root_element (nxml, &cur);

  for (cur = cur->children; cur; cur = cur->next)
    {
      if (cur->type == NXML_TYPE_ELEMENT
	  && !strcmp (cur->value, TB_CONTROLLER_GENERAL_TAG))
	{
	  for (cur = cur->children; cur; cur = cur->next)
	    {
	      if (cur->type == NXML_TYPE_ELEMENT)
		{
		  /* Log file: */
		  if (!strcmp (cur->value, TB_CONTROLLER_LOGFILE_TAG))
		    {
		      if (nxml_get_string (cur, &tmp) == NXML_OK && tmp)
			{
			  data->logfile = g_strdup (tmp);
			  free (tmp);
			}
		    }

		  /* Log Verbose: */
		  else if (!strcmp (cur->value, TB_CONTROLLER_LOGVERBOSE_TAG))
		    {
		      if (nxml_get_string (cur, &tmp) == NXML_OK && tmp)
			{
			  if (configure_log_verbose
			      (tmp, &data->logverbose, error) == FALSE)
			    {
			      free (tmp);
			      return FALSE;
			    }

			  free (tmp);
			}
		    }

		  /* Swap Directory: */
		  else if (!strcmp (cur->value, TB_CONTROLLER_SWAPDIR_TAG))
		    {
		      if (nxml_get_string (cur, &tmp) == NXML_OK && tmp)
			{
			  data->swap.directory = g_strdup (tmp);
			  free (tmp);
			}

		      if ((tmp =
			   nxmle_find_attribute (cur,
						 TB_CONTROLLER_SWAP_SIZE_ATTR,
						 NULL)))
			{
			  if (configure_parser_size
			      (tmp, &data->swap.size, error) == FALSE)
			    {
			      free (tmp);
			      return FALSE;
			    }

			  free (tmp);
			}

		      if ((tmp =
			   nxmle_find_attribute (cur,
						 TB_CONTROLLER_SWAP_FSIZE_ATTR,
						 NULL)))
			{
			  if (configure_parser_size
			      (tmp, &data->swap.filesize, error) == FALSE)
			    {
			      free (tmp);
			      return FALSE;
			    }

			  free (tmp);
			}
		    }

		  /* WaitMaxIstance: */
		  else
		    if (!strcmp
			(cur->value, TB_CONTROLLER_WAITMAXISTANCE_TAG))
		    {
		      if (nxml_get_string (cur, &tmp) == NXML_OK && tmp)
			{
			  data->wait_max_istance = atoi (tmp);
			  g_free (tmp);
			}
		    }

		  /* Background: */
		  else if (!strcmp (cur->value, TB_CONTROLLER_BACKGROUND_TAG))
		    {
		      if (nxml_get_string (cur, &tmp) == NXML_OK && tmp)
			{
			  if (!strcmp (tmp, "true"))
			    data->background = TRUE;

			  free (tmp);
			}
		    }

		  /* Pid file: */
		  else if (!strcmp (cur->value, TB_CONTROLLER_PIDFILE_TAG))
		    {
		      if (nxml_get_string (cur, &tmp) == NXML_OK && tmp)
			{
			  data->pidfile = g_strdup (tmp);
			  free (tmp);
			}
		    }

		  /* User: */
		  else if (!strcmp (cur->value, TB_CONTROLLER_USER_TAG))
		    {
		      if (nxml_get_string (cur, &tmp) == NXML_OK && tmp)
			{
			  data->user = g_strdup (tmp);
			  free (tmp);
			}
		    }

		  /* Group: */
		  else if (!strcmp (cur->value, TB_CONTROLLER_GROUP_TAG))
		    {
		      if (nxml_get_string (cur, &tmp) == NXML_OK && tmp)
			{
			  data->group = g_strdup (tmp);
			  free (tmp);
			}
		    }

		  /* Variables: */
		  else if (!strcmp (cur->value, TB_CONTROLLER_ENV_TAG))
		    {
		      if (configure_env_parser (data, cur, error) == FALSE)
			return FALSE;
		    }
		}
	    }

	  break;
	}
    }

  if (!data->logfile)
    {
      g_set_error (error, controller_error_quark (), 0,
		   "No LogFile tag in the config file.");
      return FALSE;
    }

  if (!data->pidfile)
    {
      g_set_error (error, controller_error_quark (), 0,
		   "No PidFile tag in the config file.");
      return FALSE;
    }

  if (data->swap.size >= data->swap.filesize
      || data->swap.size < strlen (TB_ISTANCE_SPLITTER))
    {
      g_set_error (error, controller_error_quark (), 0,
		   "The swap size must be more than %d and more than filesize\n",
		   (gint) strlen (TB_ISTANCE_SPLITTER));
      return FALSE;
    }

  if (!data->swap.directory)
    data->swap.directory = g_strdup (g_get_tmp_dir ());

  if (!data->swap.size)
    data->swap.size = TB_SWAP_DEFAULT;

  if (!data->swap.filesize)
    data->swap.filesize = TB_SWAP_FILE_DEFAULT;

  if (!data->wait_max_istance)
    data->wait_max_istance = TB_WAIT_MAX_ISTANCE;

  return TRUE;
}

static gboolean
configure_env_parser (TBGlobal * data, nxml_data_t * cur, GError ** error)
{
  TBEnv *env;

  gchar *tmp;
  GList *list;

  for (cur = cur->children; cur; cur = cur->next)
    {
      if (cur->type == NXML_TYPE_ELEMENT
	  && !strcmp (cur->value, TB_CONTROLLER_ENV_SINGLE_TAG))
	{
	  if (!
	      (tmp =
	       nxmle_find_attribute (cur, TB_CONTROLLER_ENV_NAME_ATTR, NULL)))
	    {
	      g_set_error (error, controller_error_quark (), 0,
			   "No " TB_CONTROLLER_ENV_NAME_ATTR
			   " attribute in a variable configuration.");
	      return FALSE;
	    }

	  for (list = data->envs; list; list = list->next)
	    {
	      env = list->data;

	      if (!strcmp (env->env, tmp))
		{
		  g_set_error (error, controller_error_quark (), 0,
			       "Duplicate variable '%s' in the config file.",
			       tmp);

		  free (tmp);
		  return FALSE;
		}
	    }

	  env = g_malloc0 (sizeof (TBEnv));
	  env->env = g_strdup (tmp);
	  free (tmp);

	  if (nxml_get_string (cur, &tmp) == NXML_OK && tmp)
	    {
	      env->value = g_strdup (tmp);
	      free (tmp);
	    }

	  data->envs = g_list_append (data->envs, env);
	}
    }

  return TRUE;
}

static gboolean
configure_log_verbose (gchar * string, LogVerbose * verbose, GError ** error)
{
  if (!strcmp (string, "error"))
    *verbose = LOG_VERBOSE_ERROR;

  else if (!strcmp (string, "warning"))
    *verbose = LOG_VERBOSE_WARNING;

  else if (!strcmp (string, "info"))
    *verbose = LOG_VERBOSE_INFO;

  else if (!strcmp (string, "debug"))
    *verbose = LOG_VERBOSE_DEBUG;

  else
    {
      g_set_error (error, controller_error_quark (), 0,
		   "LogVerbose can be 'error', 'warning', 'info', 'debug' and not '%s'.",
		   string);
      return FALSE;
    }

  return TRUE;
}

static gboolean
configure_parser_size (gchar * string, guint64 * ret_size, GError ** error)
{
  gchar *ptr = string;
  guint total = 0;
  gboolean g, m, k;
  guint gv, mv, kv;

  g = m = k = FALSE;
  gv = mv = kv = 0;

  while (ptr && *ptr)
    {
      if (*ptr >= '0' && *ptr <= '9')
	total = total * 10 + *ptr - '0';

      else
	switch (*ptr)
	  {
	  case 'k':
	  case 'K':
	    if (k == TRUE)
	      {
		g_set_error (error, controller_error_quark (), 0,
			     "The size string '%s' contains error chars.",
			     string);
		return FALSE;
	      }

	    kv = total;
	    total = 0;
	    k = TRUE;
	    break;

	  case 'm':
	  case 'M':
	    if (m == TRUE)
	      {
		g_set_error (error, controller_error_quark (), 0,
			     "The size string '%s' contains error chars.",
			     string);
		return FALSE;
	      }

	    mv = total;
	    total = 0;
	    m = TRUE;
	    break;

	  case 'g':
	  case 'G':
	    if (g == TRUE)
	      {
		g_set_error (error, controller_error_quark (), 0,
			     "The size string '%s' contains error chars.",
			     string);
		return FALSE;
	      }

	    gv = total;
	    total = 0;
	    g = TRUE;
	    break;

	  default:
	    g_set_error (error, controller_error_quark (), 0,
			 "The size string '%s' contains error chars.",
			 string);
	    return FALSE;
	  }

      ptr++;
    }

  if (kv || mv || gv || total)
    *ret_size =
      total + kv * 1024 + mv * 1024 * 1024 + gv * 1024 * 1024 * 1024;

  return TRUE;
}

static gboolean
configure_network_parser (nxml_t * nxml, TBGlobal * data, GError ** error)
{
  nxml_data_t *cur;
  gchar *tmp;

  nxml_root_element (nxml, &cur);

  for (cur = cur->children; cur; cur = cur->next)
    {
      if (cur->type == NXML_TYPE_ELEMENT
	  && !strcmp (cur->value, TB_CONTROLLER_NETWORK_TAG))
	{
	  if ((tmp =
	       nxmle_find_attribute (cur, TB_CONTROLLER_NET_ACTIVE_ATTR,
				     NULL)))
	    {
	      if (!strcmp (tmp, "true"))
		data->net_active = TRUE;

	      free (tmp);
	    }

	  for (cur = cur->children; cur; cur = cur->next)
	    {
	      if (cur->type == NXML_TYPE_ELEMENT)
		{
		  /* Interface: */
		  if (!strcmp (cur->value, TB_CONTROLLER_NET_INTERFACE_TAG))
		    {
		      if (nxml_get_string (cur, &tmp) == NXML_OK && tmp)
			{
			  data->net_interface = g_strdup (tmp);
			  free (tmp);
			}
		    }

		  /* Port: */
		  else if (!strcmp (cur->value, TB_CONTROLLER_NET_PORT_TAG))
		    {
		      if (nxml_get_string (cur, &tmp) == NXML_OK && tmp)
			{
			  data->net_port = atoi (tmp);
			  free (tmp);
			}
		    }

		  /* Listen: */
		  else if (!strcmp (cur->value, TB_CONTROLLER_NET_LISTEN_TAG))
		    {
		      if (nxml_get_string (cur, &tmp) == NXML_OK && tmp)
			{
			  data->net_listen = atoi (tmp);
			  free (tmp);
			}
		    }

		  /* MaxClients: */
		  else
		    if (!strcmp
			(cur->value, TB_CONTROLLER_NET_MAXCLIENTS_TAG))
		    {
		      if (nxml_get_string (cur, &tmp) == NXML_OK && tmp)
			{
			  data->net_maxclients = atoi (tmp);
			  free (tmp);
			}
		    }

		  /* timeout: */
		  else
		    if (!strcmp (cur->value, TB_CONTROLLER_NET_TIMEOUT_TAG))
		    {
		      if (nxml_get_string (cur, &tmp) == NXML_OK && tmp)
			{
			  data->net_timeout = atoi (tmp);
			  free (tmp);
			}
		    }

		  /* ipv6: */
		  else if (!strcmp (cur->value, TB_CONTROLLER_NET_IPV6_TAG))
		    {
		      if (nxml_get_string (cur, &tmp) == NXML_OK && tmp)
			{
			  if (!strcmp (tmp, "true"))
			    data->net_ipv6 = TRUE;

			  free (tmp);
			}
		    }
		}
	    }

	  break;
	}
    }

  if (data->net_active == FALSE)
    return TRUE;

  if (!data->net_port)
    {
      g_set_error (error, controller_error_quark (), 0,
		   "No Port tag in the config file.");
      return FALSE;
    }

  if (!data->net_timeout)
    data->net_timeout = TB_NETWORK_TIMEOUT_DEFAULT;

  if (!data->net_listen)
    data->net_listen = TB_NETWORK_LISTEN_DEFAULT;

  return TRUE;
}

/* FREE ********************************************************************/
void
configure_free (TBGlobal * data)
{
  if (!data)
    return;

  if (data->configfile)
    g_free (data->configfile);

  if (data->logfile)
    g_free (data->logfile);

  if (data->pidfile)
    g_free (data->pidfile);

  if (data->user)
    g_free (data->user);

  if (data->group)
    g_free (data->group);

  if (data->processes)
    {
      g_list_foreach (data->processes, (GFunc) process_free, NULL);
      g_list_free (data->processes);
    }

  if (data->istances)
    {
      g_list_foreach (data->istances, (GFunc) istance_free, NULL);
      g_list_free (data->istances);
    }

  if (data->swap.mutex)
    g_mutex_free (data->swap.mutex);

  if (data->swap.directory)
    g_free (data->swap.directory);

  if (data->mutex)
    g_mutex_free (data->mutex);

  if (data->net_interface)
    g_free (data->net_interface);

  g_free (data);
}

/* EOF */
